﻿'   Screenshot.vb
'   WireBear Awesemulator
'
'   Created by Chris Kent
'   Copyright 2011 WireBear. All rights reserved
'
#Region "License Information"
'   WireBear Awesemulator is free software: you can redistribute it and/or modify
'   it under the terms of the GNU General Public License as published by
'   the Free Software Foundation, either version 3 of the License, or
'   (at your option) any later version.
'
'   WireBear Awesemulator is distributed in the hope that it will be useful,
'   but WITHOUT ANY WARRANTY; without even the implied warranty of
'   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
'   GNU General Public License for more details.
'
'   You should have received a copy of the GNU General Public License
'   along with WireBear Awesemulator - License.txt
'   If not, see <http://www.gnu.org/licenses/>.
#End Region


Imports System.Drawing.Imaging

Public Class ScreenShot

    Public Const SRCCOPY As Integer = &HCC0020

#Region "PINVOKE Functions"

    Private Declare Function BitBlt Lib "gdi32" Alias "BitBlt" (ByVal hDestDC As Integer, ByVal x As Integer, ByVal y As Integer, ByVal nWidth As Integer, ByVal nHeight As Integer, ByVal hSrcDC As Integer, ByVal xSrc As Integer, ByVal ySrc As Integer, ByVal dwRop As Integer) As Integer

    Private Declare Function GetWindowDC Lib "user32" Alias "GetWindowDC" (ByVal hwnd As Integer) As Integer

    Private Declare Function GetDC Lib "user32" Alias "GetDC" (ByVal hwnd As IntPtr) As IntPtr

    Private Declare Function ReleaseDC Lib "user32" Alias "ReleaseDC" (ByVal hwnd As Integer, ByVal hdc As Integer) As Integer

    Private Declare Function GetWindowRect Lib "user32" Alias "GetWindowRect" (ByVal hWnd As Int32, ByRef lpRect As RECT) As Boolean

    Private Declare Function PrintWindow Lib "user32" Alias "PrintWindow" (ByVal hWnd As IntPtr, ByVal hdcBlt As IntPtr, ByVal nFlags As Integer) As Boolean

    Private Declare Function UpdateWindow Lib "user32" Alias "UpdateWindow" (ByVal hWnd As IntPtr) As Boolean

    Private Declare Function ShowWindow Lib "user32" Alias "ShowWindow" (ByVal hwnd As IntPtr, ByVal nCmdShow As ShowWindowCommand) As Boolean

#Region "PINVOKE Enums"

    Enum ShowWindowCommand As Integer
        ''' <summary>
        ''' Hides the window and activates another window.
        ''' </summary>
        Hide = 0
        ''' <summary>
        ''' Activates and displays a window. If the window is minimized or 
        ''' maximized, the system restores it to its original size and position.
        ''' An application should specify this flag when displaying the window 
        ''' for the first time.
        ''' </summary>
        Normal = 1
        ''' <summary>
        ''' Activates the window and displays it as a minimized window.
        ''' </summary>
        ShowMinimized = 2
        ''' <summary>
        ''' Maximizes the specified window.
        ''' </summary>
        Maximize = 3
        ' is this the right value?
        ''' <summary>
        ''' Activates the window and displays it as a maximized window.
        ''' </summary>       
        ShowMaximized = 3
        ''' <summary>
        ''' Displays a window in its most recent size and position. This value 
        ''' is similar to <see cref="ShowWindowCommand.Normal"/>, except 
        ''' the window is not actived.
        ''' </summary>
        ShowNoActivate = 4
        ''' <summary>
        ''' Activates the window and displays it in its current size and position. 
        ''' </summary>
        Show = 5
        ''' <summary>
        ''' Minimizes the specified window and activates the next top-level 
        ''' window in the Z order.
        ''' </summary>
        Minimize = 6
        ''' <summary>
        ''' Displays the window as a minimized window. This value is similar to
        ''' <see cref="ShowWindowCommand.ShowMinimized"/>, except the 
        ''' window is not activated.
        ''' </summary>
        ShowMinNoActive = 7
        ''' <summary>
        ''' Displays the window in its current size and position. This value is 
        ''' similar to <see cref="ShowWindowCommand.Show"/>, except the 
        ''' window is not activated.
        ''' </summary>
        ShowNA = 8
        ''' <summary>
        ''' Activates and displays the window. If the window is minimized or 
        ''' maximized, the system restores it to its original size and position. 
        ''' An application should specify this flag when restoring a minimized window.
        ''' </summary>
        Restore = 9
        ''' <summary>
        ''' Sets the show state based on the SW_* value specified in the 
        ''' STARTUPINFO structure passed to the CreateProcess function by the 
        ''' program that started the application.
        ''' </summary>
        ShowDefault = 10
        ''' <summary>
        '''  <b>Windows 2000/XP:</b> Minimizes a window, even if the thread 
        ''' that owns the window is not responding. This flag should only be 
        ''' used when minimizing windows from a different thread.
        ''' </summary>
        ForceMinimize = 11
    End Enum

#End Region

#End Region

#Region "Win32 RECT structure and wrappers"

    Structure RECT
        Public Left As Int32
        Public Top As Int32
        Public Right As Int32
        Public Bottom As Int32
    End Structure

    Public Shared Function RectWidth(ByVal theRect As RECT) As Integer
        Return theRect.Right - theRect.Left
    End Function

    Public Shared Function RectHeight(ByVal theRect As RECT) As Integer
        Return theRect.Bottom - theRect.Top
    End Function

#End Region


    ''' <summary>
    ''' Gets a Handle for a process with a given name
    ''' </summary>
    ''' <param name="ProcessName">The name of the process whose name should be returned</param>
    ''' <returns>A pointer to the process</returns>
    Public Shared Function ProcessHandle(ByVal ProcessName As String) As IntPtr
        Dim processes() As Process = Process.GetProcessesByName(ProcessName)
        If processes IsNot Nothing AndAlso processes.Count > 0 Then
            Using myProcess As Process = processes(0)
                Return myProcess.MainWindowHandle
            End Using
        Else
            Throw New Exception(String.Format("Process {0} Not Found!", ProcessName))
        End If
    End Function

    ''' <summary>
    ''' Lousy work around to ensure window is fully visible (Minimizes then restores)
    ''' </summary>
    ''' <param name="Hwnd">The window to bring to front</param>
    Public Shared Sub BringWindowToFront(ByVal Hwnd As IntPtr)
        ShowWindow(Hwnd, ShowWindowCommand.Minimize)
        ShowWindow(Hwnd, ShowWindowCommand.Restore)
    End Sub

    ''' <summary>
    ''' Gets the size and location of a window
    ''' Wraps the Win32 Rect Structure into an actual Rectangle object
    ''' </summary>
    ''' <param name="Hwnd">The window to evaluate</param>
    Public Shared Function WindowRectangle(ByVal Hwnd As IntPtr) As Rectangle
        Dim wRect As New RECT
        GetWindowRect(Hwnd, wRect)
        Return New Rectangle(wRect.Left, wRect.Top, RectWidth(wRect), RectHeight(wRect))
    End Function

    ''' <summary>
    ''' Gets the size of a window
    ''' </summary>
    ''' <param name="Hwnd">The window to evaluate</param>
    Public Shared Function WindowSize(ByVal Hwnd As IntPtr) As Size
        Dim wRect As New RECT
        GetWindowRect(Hwnd, wRect)
        Return New Size(RectWidth(wRect), RectHeight(wRect))
    End Function

    ''' <summary>
    ''' Gets a screenshot of a window
    ''' </summary>
    ''' <param name="Hwnd">The window to capture</param>
    ''' <param name="Portion">Relative portion of window to capture (pass nothing to get it all)</param>
    Public Shared Function GetWindow(ByVal Hwnd As IntPtr, Optional ByVal Portion As Rectangle = Nothing) As Bitmap
        Dim wRect As New RECT
        GetWindowRect(Hwnd, wRect)
        Dim wSize As New Size(RectWidth(wRect), RectHeight(wRect))
        Dim wPos As New Point(wRect.Left, wRect.Top)

        Dim FullScreen As Bitmap
        If Screen.AllScreens.Count > 1 Then
            FullScreen = GetAllScreens()
        Else
            FullScreen = CreateBitmap()
        End If

        If Portion = Nothing Then Portion = New Rectangle(0, 0, wSize.Width, wSize.Height)
        Dim SrcRect As New Rectangle(wPos.X + Portion.X, wPos.Y + Portion.Y, Portion.Width, Portion.Height)
        Dim DestRect As New Rectangle(0, 0, Portion.Width, Portion.Height)

        Dim Win As New Bitmap(Portion.Width, Portion.Height)
        Using g As Graphics = Graphics.FromImage(Win)
            g.DrawImage(FullScreen, DestRect, SrcRect, GraphicsUnit.Pixel)
        End Using

        Return Win
    End Function

    ''' <summary>
    ''' Gets whole screen image (When logged in) - Simple
    ''' </summary>
    ''' <returns>Bitmap image of Primary Monitor</returns>
    Public Shared Function CreateBitmap() As Bitmap
        Dim bmp As Bitmap = New Bitmap(Screen.PrimaryScreen.Bounds.Width, Screen.PrimaryScreen.Bounds.Height)
        Using gDest As Graphics = Graphics.FromImage(bmp)
            Dim hdcSrc As Integer = GetDC(Nothing).ToInt32
            Dim hdcDest As IntPtr = gDest.GetHdc
            BitBlt(hdcDest.ToInt32, 0, 0, Screen.PrimaryScreen.Bounds.Width, Screen.PrimaryScreen.Bounds.Height, hdcSrc, 0, 0, SRCCOPY)
            gDest.ReleaseHdc(hdcDest)
            ReleaseDC(Nothing, hdcSrc)
        End Using

        Return bmp
    End Function

    ''' <summary>
    ''' Gets all Screens (For use with multiple monitors)
    ''' </summary>
    ''' <returns>Bitmap image of ALL monitors</returns>
    Public Shared Function GetAllScreens() As Bitmap
        Dim maxHeight As Integer = 0
        Dim maxWidth As Integer = 0
        For Each scr As Screen In Screen.AllScreens
            maxWidth += scr.Bounds.Width
            If scr.Bounds.Height > maxHeight Then maxHeight = scr.Bounds.Height
        Next

        Dim allScreensCapture As New Bitmap(maxWidth, maxHeight, PixelFormat.Format24bppRgb)
        Using g2 As Graphics = Graphics.FromImage(allScreensCapture)
            Dim a As New Point(0, 0)
            For Each scr As Screen In Screen.AllScreens
                Dim screenSize As Size = New Size(scr.Bounds.Width, scr.Bounds.Height)
                Using screenGrab As Bitmap = New Bitmap(scr.Bounds.Width, scr.Bounds.Height)
                    Using g As Graphics = Graphics.FromImage(screenGrab)
                        g.CopyFromScreen(a, New Point(0, 0), screenSize)
                    End Using
                    g2.DrawImage(screenGrab, a)
                End Using
                a.X += scr.Bounds.Width
            Next
        End Using

        Return allScreensCapture
    End Function


    ''' <summary>
    ''' Gets all windows and prints them when available to a single large bitmap
    ''' Process names are also printed between images
    ''' </summary>
    ''' <returns>A long bitmap with all available windows</returns>
    Public Shared Function GetAllWindows() As Bitmap
        Const TextSpace As Integer = 24
        Dim bmps As New Collections.Generic.Dictionary(Of String, Bitmap)

        'Collect Bitmaps
        For Each p As Process In Process.GetProcesses()
            Dim SrcRect As RECT
            If GetWindowRect(p.MainWindowHandle.ToInt32, SrcRect) Then
                Dim SrcWidth As Integer = SrcRect.Right - SrcRect.Left
                If SrcWidth <= 0 Then SrcWidth = My.Computer.Screen.WorkingArea.Width
                Dim SrcHeight As Integer = SrcRect.Bottom - SrcRect.Top
                If SrcHeight <= 0 Then SrcHeight = My.Computer.Screen.WorkingArea.Height

                'Ensure unique entries
                Dim basekey As String = String.Format("[{0}] - {1}", p.ProcessName, p.MainWindowTitle)
                If String.IsNullOrEmpty(basekey) Then basekey = "Unknown"
                Dim key As String = basekey
                Dim i As Integer = 0
                While bmps.ContainsKey(key)
                    i += 1
                    key = String.Format("{0} ({1})", basekey, i)
                End While

                bmps.Add(key, CreateBitmap(p.MainWindowHandle.ToInt32, SrcWidth, SrcHeight))
            End If
        Next

        'Determine final image bounds
        Dim maxHeight As Integer = 0
        Dim maxWidth As Integer = 0
        For Each b As KeyValuePair(Of String, Bitmap) In bmps
            maxHeight += b.Value.Height + TextSpace
            If b.Value.Width > maxWidth Then maxWidth = b.Value.Width
        Next

        'Draw all windows to one big image
        Dim allWindows As New Bitmap(maxWidth, maxHeight, PixelFormat.Format24bppRgb)
        Using g As Graphics = Graphics.FromImage(allWindows)
            g.FillRectangle(Brushes.CornflowerBlue, 0, 0, maxWidth, maxHeight)
            Dim a As New Point(0, 0)
            For Each b As KeyValuePair(Of String, Bitmap) In bmps
                g.DrawString(b.Key, New Font("Arial", 12), Brushes.Black, 0, a.Y + 5)
                a.Y += TextSpace
                g.DrawImage(b.Value, a)
                a.Y += b.Value.Height
            Next
        End Using

        Return allWindows
    End Function

    ''' <summary>
    ''' Prints a window screenshot to a bitmap
    ''' </summary>
    ''' <param name="hWnd">The window to capture</param>
    ''' <param name="SrcWidth">The width of the image</param>
    ''' <param name="SrcHeight">The height of the image</param>
    Public Shared Function CreateBitmap(ByVal hWnd As Integer, ByVal SrcWidth As Integer, ByVal SrcHeight As Integer) As Bitmap
        Dim bmp As Bitmap = New Bitmap(SrcWidth, SrcHeight)
        Using gDest As Graphics = Graphics.FromImage(bmp)
            Dim hdcDest As IntPtr = gDest.GetHdc
            UpdateWindow(hWnd)
            PrintWindow(hWnd, hdcDest, 0)
            gDest.ReleaseHdc(hdcDest)
        End Using

        Return bmp
    End Function

End Class


